/* 
 *  Copyright 2012 CodeMagi, Inc.
 * 
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package com.codemagi.servlets;

import com.Ostermiller.util.Base64;
import com.codemagi.database.*;
import com.codemagi.login.*;
import com.codemagi.login.model.*;
import com.codemagi.servlets.model.*;
import com.codemagi.servlets.upload.model.Upload;
import com.codemagi.servlets.upload.util.*;
import com.codemagi.servlets.validation.*;
import com.codemagi.util.*;
import java.io.*;
import java.net.*;
import java.text.*;
import java.util.*;
import java.util.regex.*;
import javax.crypto.Cipher;
import javax.net.ssl.*;
import javax.servlet.*;
import javax.servlet.http.*;
import javax.xml.transform.*;
import javax.xml.transform.stream.*;
import org.apache.commons.collections.map.MultiKeyMap;
import org.apache.commons.fileupload.*;
import org.apache.commons.fileupload.servlet.*;
import org.apache.log4j.*;
import org.castor.jdo.engine.DatabaseRegistry;
import org.exolab.castor.jdo.CacheManager;
import org.exolab.castor.jdo.Database;
import org.exolab.castor.jdo.JDOManager;
import org.exolab.castor.jdo.OQLQuery;
import org.exolab.castor.jdo.PersistenceException;
import org.exolab.castor.jdo.QueryResults;
import org.exolab.castor.mapping.Mapping;
import org.exolab.castor.xml.*;
import sun.net.www.protocol.ftp.FtpURLConnection;

/**
 * BaseServet is the core of the CodeMagi web application framework. All controller servlets
 * should extend from BaseServlet to get access to the JDO database and input validation methods.
 *
 *  @version 1.0
 *  @author August Detlefsen for CodeMagi, Inc.
 */
public class BaseServlet extends HttpServlet {

    static Logger log = Logger.getLogger("com.codemagi.servlets.BaseServlet");

    //constants
    protected static final String WEB_INF_DIR = "WEB-INF/";
    protected static final String ETC_DIR     = "etc/";
    protected static final String ICONS_DIR   = "icons/";

    protected static final String MAPPING_FILE  = "mapping.xml";
    protected static final String DATABASE_FILE = "database.xml";

    public static final String STD_HTTP_PORT    = "80";
    public static final String STD_HTTPS_PORT   = "443";

    //MEMBERS
    protected static long MAX_UPLOAD_SIZE = 1028 * 1028 * 64; //64 MB
    protected String userClass    = "com.codemagi.login.model.User";


    //VIEWS
    protected static final String VIEW_ROOT          = "/";
    protected static String VIEW_MAIN                = "/";  
    protected static String VIEW_LOGIN               = "/login/index.jsp";
    protected static String VIEW_ADMIN_USERS         = "/login/admin_users.jsp";
    protected static String VIEW_MODIFY_USER         = "/login/admin_users_modify.jsp";
    protected static String VIEW_ACCESS_DENIED       = "/access_denied.jsp";

    /**
     * Used to indicate that no forward should occur at the end of the service method
     */
    protected static final String VIEW_NONE                = "NONE";  


    //messages
    protected static String MSG_STRING_LENGTH_ERROR        = "Invalid field length. The maximum length for this field is ";
    protected static final String MSG_UNKNOWN_ERROR        = "Could not perform action: An unknown error has occurred.";
    protected static final String MSG_DATABASE_ERROR       = "Could not perform action: A database error has occurred. This could be due to a concurrent modification of the item you were editing. Please try resubmitting your edits.";
    protected static final String MSG_LDAP_ERROR           = "Could not perform action: An LDAP error has occurred.";
    protected static final String MSG_IO_ERROR             = "Could not perform action: A file I/O error has occurred.";
    protected static final String MSG_RUNTIME_ERROR        = "Could not perform action: A runtime error has occurred.";
    protected static final String MSG_MAPPING_ERROR        = "Could not perform action: A mapping error has occurred.";
    protected static final String MSG_XML_ERROR            = "Could not perform action: An XML error has occurred.";

    protected static final String MSG_INVALID_INTEGER      = "Invalid number entered. Please check your data and try again.";
    protected static final String MSG_INVALID_LONG         = "Invalid number entered. Please check your data and try again.";
    protected static final String MSG_INVALID_FLOAT        = "Invalid number entered. Please check your data and try again.";
    protected static final String MSG_INVALID_DOUBLE       = "Invalid number entered. Please check your data and try again.";
    protected static final String MSG_INVALID_EMAIL        = "Invalid email address (please check the address and try again): ";
    protected static final String MSG_MISSING_REQUIRED     = "Please enter all required information.";
    protected static final String MSG_MISSING_TOKEN        = "Missing required token. This could be an attempt to compromise your account. Please contact the site administrators at once.";
    protected static final String MSG_NOT_MULTIPART        = "The request is not Multipart/form-data.";

    protected static final String MSG_INVALID_PASSWORD     = "Invalid username/password combination.";
    protected static final String MSG_USER_EXPIRED         = "Account expired.";
    protected static String MSG_USER_DISABLED        = "Account disabled by administrator.";
    protected static final String MSG_USER_UPDATED         = "User profile updated successfully.";
    protected static final String MSG_INVALID_USERNAME     = "Invalid username/password combination.";
    protected static final String MSG_PASSWORD_MISMATCH    = "Password and confirmation password must match";
    protected static final String MSG_PREEXISTING_USERNAME = "That username is already in use. Please choose another.";
    protected static final String MSG_USER_ADDED           = "New user created. Thank you for adding any additional user information.";

    protected static final String MSG_MAKE_DIR_FAILED      = "Unable to create directory: ";
    protected static final String MSG_PROPERTIES_NOT_FOUND = "Properties file not found.";
    protected static final String MSG_PROPERTIES_ERROR     = "Error in properties file.";
    protected static final String MSG_UNABLE_TO_SECURE     = "ERROR: This request requires SSL.";
    protected static final String MSG_REDIRECTED_SECURE    = "Request forwarded to https.";

    protected static final String MSG_INVALID_DATE         = "Invalid date entered. Acceptable dates are in the format: MM/DD/YYYY";

    //access levels
    protected static final String ACCESS_SUPERUSER         = "SUPERUSER";

    //members
    //location of this servlet
    protected static String location = "";

    //configuration parameters
    protected String SMTP_HOST         = "localhost";
    private String httpPort            = STD_HTTP_PORT;
    private String httpsPort           = STD_HTTPS_PORT;
    private String httpsHeaderName     = null;
    private String httpsHeaderValue    = null;

    private boolean useMultipartBuffer = true;
    private static boolean verifyHostnames    = true;

    //application context
    protected static ServletContext application;

    //JDO instance
    protected Mapping mapping;
    protected static XMLContext XML_CONTEXT     = new XMLContext();
    private static Map<String,Mapping> MAPPINGS = new HashMap<String,Mapping>(1);
    protected static JDOManager jdo;

    //Date format used for parsing form input dates
    protected static final SimpleDateFormat CALENDAR_FORMAT     = new SimpleDateFormat("MM/dd/yyyy");
    static {
        CALENDAR_FORMAT.setLenient(false);
    }

    //Collection to hold input validation routines
    private MultiKeyMap validators = new MultiKeyMap();

    //Collection to hold admin links 
    private ArrayList<AdminLink> adminLinks = new ArrayList<AdminLink>();

    /**
     * Initializes the servlet when it is loaded by the
     * servlet engine.
     *
     * @param config the configuration as <code>ServletConfig</code>
     *
     * @throws ServletException if initialization fails.
     */
    @Override
    public void init(ServletConfig config)
        throws ServletException {

        super.init(config);
        log.info(this.getClass().getName() + " - init");

	//get a handle on the application context
	application = config.getServletContext();
	log.debug("context: " + application.toString());

	//get startup params
	httpPort = (String)application.getInitParameter("securelogin.http.port");

        if (Utils.isEmpty(httpPort)) {
            httpPort = STD_HTTP_PORT;
        }

        httpsPort = (String)application.getInitParameter("securelogin.https.port");
        if (Utils.isEmpty(httpsPort)) {
            httpsPort = STD_HTTPS_PORT;
        }
        httpsHeaderName  = (String)application.getInitParameter("securelogin.https.headername");
        httpsHeaderValue = (String)application.getInitParameter("securelogin.https.headervalue");

	log.debug("HTTP PORT: " + httpPort + " HTTPS PORT: " + httpsPort);
	log.debug("HTTPS HEADER: " + httpsHeaderName + " VALUE: " + httpsHeaderValue);


	String pUseMultipartBuffer = config.getInitParameter("useMultipartBuffer");
	if ( !Utils.isEmpty(pUseMultipartBuffer) ) useMultipartBuffer = Boolean.valueOf(pUseMultipartBuffer).booleanValue();

	String pVerifyHostnames    = config.getInitParameter("verifyHostnames");
	if ( !Utils.isEmpty(pVerifyHostnames) ) verifyHostnames = Boolean.valueOf(pVerifyHostnames).booleanValue();

        //build JDO, get DB connection
        try {

	    //get this servlet location
	    //getRealPath() is not portable 
	    location = application.getRealPath("/");
	    if (location == null) location = application.getResource("/").toString();
	    log.debug("location: " + location);

	    //read config params from context
	    Stopwatch timer = new Stopwatch();
	    timer.start();

	    String mappingFile = getServletConfig().getInitParameter("mapping.xml");
	    log.debug("mapping file from param: " + mappingFile);
	    if (mappingFile == null) mappingFile = MAPPING_FILE;
	   
	    mapping = MAPPINGS.get(mappingFile);
	    if (mapping == null) {
		URL mappingUrl = application.getResource("/" + WEB_INF_DIR + ETC_DIR + mappingFile);
		log.debug("mapping URL: " + mappingUrl.toString());
		
		mapping = new Mapping( BaseServlet.class.getClassLoader() );
		
		log.debug("about to load mapping");
		mapping.loadMapping( mappingUrl );
		log.debug(" ...done");

		MAPPINGS.put(mappingFile, mapping);
		XML_CONTEXT.addMapping(mapping);
	    }

	    timer.stop();
	    log.debug("Loaded mappings in: " + timer.getElapsedTime());

	    String databaseFile = getServletConfig().getInitParameter("database.xml");
	    log.debug("database file from param: " + databaseFile);
	    if (databaseFile == null) databaseFile = DATABASE_FILE;
	    
	    //String databaseFileLocation = location + WEB_INF_DIR + ETC_DIR + databaseFile;
	    URL databaseUrl = application.getResource("/" + WEB_INF_DIR + ETC_DIR + databaseFile);
	    String databaseFileLocation = databaseUrl.toString();
	    
	    log.debug("database file location: " + databaseFileLocation);

	    String databaseName = getServletConfig().getInitParameter("database.name");
	    log.debug("databaseName: " + databaseName);
	    if (databaseName == null) databaseName = "database";
	    
	    if ( !DatabaseRegistry.isDatabaseRegistred(databaseName) ) {
		log.debug("about to load JDO config from: " + databaseFileLocation);
		JDOManager.loadConfiguration(databaseFileLocation, BaseServlet.class.getClassLoader());
		log.debug(" ...done");
	    } else {
		log.debug("JDO Config already loaded -SKIPPING");
	    }

	    log.debug("about to create JDO instance: " + databaseName);
	    jdo = JDOManager.createInstance(databaseName);
	    log.debug(" ...done");
	    log.debug("JDO: " + jdo);
	    application.setAttribute("jdo", jdo);

	    //call the install function to initialize the servlet for the first time
	    install();

	    //place a reference to this servlet in the application context                           
	    application.setAttribute(config.getServletName(), this);

        } catch (Exception e) {
            log.debug("", e);

            throw new ServletException(e);
        }

    }


    /**
     * Override this method to perform any initialization tasks that need to be run the first time the servlet starts up
     */
    protected void install() throws Exception {
	//no-op
    }


    //MULTIPART REQUEST HANDLERS ---------------------------------------------------------------------

    /**
     * Parses a multipart request into a Hashtable
     */
    public static Map parseCommonsMultipart(HttpServletRequest request) 
	throws AppException, org.apache.commons.fileupload.FileUploadException {

	if ( !ServletFileUpload.isMultipartContent(request) ) throw new AppException(MSG_NOT_MULTIPART);

	Map output = new HashMap();

	// Create a listener to monitor the file upload
	UploadListener listener = new UploadListener(request, 0);

	// Create a factory for disk-based file items
	FileItemFactory factory = new MonitoredDiskFileItemFactory(listener);

	// Create a new file upload handler
	ServletFileUpload upload = new ServletFileUpload(factory);
	
	//set the maximum allowable size of the upload
	upload.setSizeMax(MAX_UPLOAD_SIZE);

	// Parse the request
	List items = upload.parseRequest(request);

	Iterator i = items.iterator();
	while (i.hasNext()) {
	    FileItem item = (FileItem)i.next();
	    
	    if (item.isFormField()) {
		String itemValue = item.getString();
		log.debug("PARAM PART! - " + item.getFieldName() + " = " + itemValue);

		//check for existing for field
		Object existingItem = output.get(item.getFieldName());
		if (existingItem == null) {
		    output.put(item.getFieldName(), itemValue );

		} else if (existingItem instanceof String) {
		    ArrayList<String> itemList = new ArrayList<String>();
		    itemList.add( (String)existingItem );
		    itemList.add( itemValue );

		    output.put(item.getFieldName(), itemList );

		} else {
		    ((List)existingItem).add(itemValue);

		    output.put(item.getFieldName(), existingItem);

		}

	    } else {
		output.put(item.getFieldName(), item);

		log.debug("FILE PART! - " + item.getFieldName() );
	    }
	}

	//set the params into the request attributes in case they are needed for taglibs
	request.setAttribute("requestParams", output);

	return output;
    }
    
    
    /**
     * Parses an http query String (joe=bob&sally=sue) into a HashMap
     *
     * NOTE: If the query string contains multiple parameters with the same name, they could be overwritten.
     */
    protected static Map<String,String> parseQueryString(String input) {
	Map<String,String> output = new HashMap<String,String>();

	if (Utils.isEmpty(input)) return output;

	String[] pairs = input.split("&");

	for (int i = 0; i < pairs.length; i++) {
	    String pair = pairs[i];

	    int index = pair.indexOf("=");

	    String name  = (index <= 0) ? pair : pair.substring(0, index);
	    String value = (index <= 0) ? "" : pair.substring(index+1) ;

	    log.debug("name: " + name + " value: " + value);
	    output.put(name, value);
	}

	return output;
    }




    //VARIABLES ----------------------------------------------------------------------------------

    /**
     * Sets a variable in the persistence layer
     */
    public void set(String name, String value) {

	//sanity check 
	if (Utils.isEmpty(name)) return;

	Database db = null; 

	try {
	    db = getJDODatabase();
	    db.begin();

	    Variable var = null; 

	    OQLQuery oql = db.getOQLQuery(" SELECT v FROM com.codemagi.servlets.model.Variable v " + 
					  " WHERE v.name = $1 ");
	    oql.bind(name);

	    QueryResults results = oql.execute();
	    if (results.hasMore()) {
		var = (Variable)results.next();
		var.setValue(value);
	    } else {
		var = new Variable();
		var.setName(name);
		var.setValue(value);

		db.create(var);
	    }

	    db.commit();

	} catch (PersistenceException pe) {
	    handlePersistenceException(db, pe);

	} finally {
	    closeJDODatabase(db);

	}
    }

    /**
     * Sets a variable in the persistence layer if it does not exist already
     */
    public void setOnce(String name, String value) {

	//sanity check 
	if (Utils.isEmpty(name)) return;

	Database db = null; 

	try {
	    db = getJDODatabase();
	    db.begin();

	    OQLQuery oql = db.getOQLQuery(" SELECT v FROM com.codemagi.servlets.model.Variable v " + 
					  " WHERE v.name = $1 ");
	    oql.bind(name);

	    QueryResults results = oql.execute(Database.ReadOnly);
	    if (results.hasMore()) return;

	    //variable does not yet exist, add it
	    Variable var = new Variable();
	    var.setName(name);
	    var.setValue(value);
	    
	    db.create(var);

	    db.commit();

	} catch (PersistenceException pe) {
	    handlePersistenceException(db, pe);

	} finally {
	    closeJDODatabase(db);

	}
    }

    /**
     * Retrieves a variable from the persistence layer
     */
    public String get(String name) {

	//sanity check 
	if (Utils.isEmpty(name)) return "";

	Database db = null; 

	try {
	    db = getJDODatabase();
	    db.begin();

	    OQLQuery oql = db.getOQLQuery(" SELECT v FROM com.codemagi.servlets.model.Variable v " + 
					  " WHERE v.name = $1 ");
	    oql.bind(name);

	    QueryResults results = oql.execute(Database.ReadOnly);
	    if (results.hasMore()) {
		Variable var = (Variable)results.next();

		return Utils.noNulls(var.getValue());
	    }

	} catch (PersistenceException pe) {
	    handlePersistenceException(db, pe);

	} finally {
	    closeJDODatabase(db);

	}

	return "";
    }

    public long getMaxUploadSize() {
	return MAX_UPLOAD_SIZE;
    }

    //ADMIN LINK UTILITY METHODS -----------------------------------------------------------------

    public void addAdminLink(AdminLink newValue) {
	if (newValue == null) return;

	if (!adminLinks.contains(newValue)) adminLinks.add(newValue);
    }

    public List<AdminLink> getAdminLinks() {
	return adminLinks;
    }


    //NODE UTILITY METHODS -----------------------------------------------------------------------

    /**
     * Sets node-specific fields on an insert or update. The following fields are set:
     * created by, created date, modified by, modified date, class name, ip address 
     *
     * If a Node implements the Searchable interface, an entry will also be added or updated
     * in the search index.
     */
    protected void setNodeFields(Node node, HttpServletRequest request, Database db) 
	throws PersistenceException {

	setNodeFields(node, request, db, false);
    }

    /**
     * Sets node-specific fields on an insert or update. The following fields are set:
     * created by, created date, modified by, modified date, class name, ip address 
     *
     * If a Node implements the Searchable interface, an entry will also be added or updated
     * in the search index.
     * 
     * @param recursive    If true, setNodeFields will also be called on the Node's parent(s)
     */
    protected void setNodeFields(Node node, HttpServletRequest request, Database db, boolean recursive) 
	throws PersistenceException {

	log.debug("setNodeFields() userClass: " + userClass);

	//sanity check
	if (node == null || request == null || db == null) return;

	//load the current user from the session
	HttpSession session = request.getSession();
	IUser user = (IUser)session.getAttribute("user");
	if (user != null && user.getId() != null) {
	    try {
		user = (IUser)db.load(Class.forName(userClass), user.getId());

	    } catch (ClassNotFoundException cnfe) {
		log.debug("", cnfe);

	    }
	}

	//if this is a create, set created by, else set modified by
	if (node.getId() == null) {
	    node.setCreatedBy(user);
	    node.setCreatedDate(new Date());
            node.setModifiedDate(new Date());

	    //set the class name of the new node
	    node.setClassName(node.getClass().getName());

	} else {
	    node.setModifiedBy(user);
            node.setModifiedDate(new Date());

        }

	//store the IP address of the creator/modifier
	node.setIpAddress( request.getRemoteAddr() );

	//create the node in the db if it is not already
	if (node.getId() == null) db.create(node);

	//if this node implements Searchable, update the index
	if (node instanceof Searchable) {
	    String searchText = ((Searchable)node).getSearchText();
	    log.debug("Updating search index with text: " + searchText);

	    SearchIndexEntry indexEntry = null;

	    OQLQuery oql = db.getOQLQuery(" SELECT e FROM com.codemagi.servlets.model.SearchIndexEntry e " + 
					  " WHERE e.id = $1 ");
	    oql.bind(node.getId());

	    QueryResults results = oql.execute();
	    if (results.hasMore()) {
		indexEntry = (SearchIndexEntry)results.next();
		indexEntry.setName(searchText);

	    } else {
		indexEntry = new SearchIndexEntry();
		indexEntry.setId(node.getId());
		indexEntry.setName(searchText);
		db.create(indexEntry);
	    }
	    log.debug("Search index entry: " + indexEntry);
	}

	//if this node has a parent, update the parent as well 
	if (recursive) setNodeFields(node.getParent(), request, db, recursive);
    }


    /**
     * Sets node-specific fields on an insert or update. The following fields are set:
     * created date, modified date, class name
     */
    protected void setNodeFields(Node node) 
	throws PersistenceException {

	//sanity check
	if (node == null) return;

	//if this is a create, set created by, else set modified by
	if (node.getId() == null) {
	    node.setCreatedDate(new Date());
            node.setModifiedDate(new Date());

	    //set the class name of the new node
	    node.setClassName(node.getClass().getName());

	} else {
            node.setModifiedDate(new Date());

        }
    }


    /**
     * Returns the parents of a Node
     *
     * @deprecated    Calling OQL on the Node class causes AIOOBE? Use getParents(Node node, Database db, Class type) instead.
     */
    public static List<Node> getParents(Node node, Database db) 
	throws PersistenceException {

	log.debug("getParents( " + node + ", db, com.codemagi.servlets.model.Node )");

	ArrayList<Node> output = new ArrayList<Node>();

        //sanity check                                                                                                          
        if (node == null || db == null) return output;
	
	log.debug("    passed null checks");

	OQLQuery oql = db.getOQLQuery(" SELECT n FROM com.codemagi.servlets.model.Node n " + 
				      " WHERE n.children.id = $1 ");
	oql.bind(node.getId());

	QueryResults results = oql.execute();

	while (results.hasMore()) {
	    Node parent = (Node)results.next();

	    log.debug("    parent: " + parent);

	    output.add(parent);
	} 

	return output;
    }


    /**
     * Returns the parents of a Node that are of a particular class
     */
    public static List<Node> getParents(Node node, Database db, Class type) 
	throws PersistenceException {

	log.debug("getParents( " + node + ", db, " + type.getName() + " )");

	ArrayList<Node> output = new ArrayList<Node>();

        //sanity check                                                                                                          
        if (node == null || db == null || type == null) return output;

	log.debug("    passed null checks");

	OQLQuery oql = db.getOQLQuery(" SELECT n FROM " + type.getName() + " n " + 
				      " WHERE n.children.id = $1 ");
	oql.bind(node.getId());

	QueryResults results = oql.execute();

	while (results.hasMore()) {
	    Node parent = (Node)results.next();

	    log.debug("    parent: " + parent);

	    output.add(parent);
	} 

	return output;
    }


    /** 
     * Returns a Node with the specified ID
     */
    public Node getNode(Integer id) {
	if (id == null) return null;

	Database db = null;

	try {
	    db = getJDODatabase();
	    db.begin();

	    return (Node)db.load(Node.class, id, Database.ReadOnly);

	} catch (PersistenceException pe) {
	    handlePersistenceException(db, pe);
	    
	} finally {
	    closeJDODatabase(db);
	}

	return null;
    }

    /** 
     * Returns a Node with the specified ID. This Node is loaded with a Read-Write Database access level.
     */
    public Node getNode(Integer id, Database db) throws PersistenceException {
	if (id == null || db == null) return null;

	if (!db.isActive()) db.begin();

	Node node = (Node)db.load(Node.class, id);

	return node;
    }


    /**
     * create a parent/child relationship between two nodes
     */
    public void createRelationship(Integer parentId, Integer childId) {
	log.debug("createRelationship: parentId: " + parentId + " - childId: " + childId);

	DataSourceBean dataBean = new DataSourceBean( application.getInitParameter("database") );

	dataBean.setQuery("INSERT INTO node_node_xref (parent_id, child_id) VALUES (" + parentId + ", " + childId + ")");

	dataBean.executeQuery();
    }
    

    /**
     * remove a parent/child relationship between two nodes
     */
    public void removeRelationship(Integer parentId, Integer childId) {
	DataSourceBean dataBean = new DataSourceBean( application.getInitParameter("database") );

	dataBean.setQuery("DELETE FROM node_node_xref WHERE parent_id = " + parentId + " AND child_id = " + childId );

	dataBean.executeQuery();
    }


    //NAVIGATION METHODS ---------------------------------------------------------------------------------------------

    /**
     * Handles a LoginException: Performs any actions that need to happen when catching a LoginException in service() method.
     *
     * @return String    The next page to forward to
     */
    protected String handleLoginException(HttpServletRequest request, HttpServletResponse response, LoginException le)
	throws IOException, ServletException {

	log.debug("handleLoginException()", le);   

	//if the exception contains an error message, set that into request
	request.setAttribute("error_message", le.getMessage());

	//first try to forward to user module
	ServletContext tmpl = getServletContext().getContext("/user");                                                
	if (tmpl != null) {
	    tmpl.getRequestDispatcher("/index.jsp").forward(request, response);                                          
	    return VIEW_NONE;
	}

	//otherwise send them to conventional view
	return VIEW_LOGIN;
    }


    /**
     * Handles an AccessException: Performs any actions that need to happen when catching an AccessException in service() method.
     *
     * @return String    The next page to forward to
     */
    protected String handleAccessException(HttpServletRequest request, HttpServletResponse response, AccessException ae) 
	throws IOException, ServletException {

	log.fatal("*** Access Exception ***", ae);
                         
	//first try to forward to user module
	ServletContext tmpl = getServletContext().getContext("/user");
	if (tmpl != null) {
	    tmpl.getRequestDispatcher("/access_denied.jsp").forward(request, response);
	    return VIEW_NONE;
	}

	//otherwise send them to conventional view
	return VIEW_ACCESS_DENIED;
    }


    /**
     * Handles a PersistenceException: Performs any actions that need to happen
     * when catching a PersistenceException in a dispatcher.
     */
    protected void handlePersistenceException(HttpServletRequest request, Database db, PersistenceException pe) {
	handlePersistenceException(request, db, pe, MSG_DATABASE_ERROR);
    }


    /**
     * Handles a PersistenceException: Performs any actions that need to happen when 
     * catching a PersistenceException in a dispatcher.
     */
    protected void handlePersistenceException(HttpServletRequest request, Database db, PersistenceException pe, String message) {

	log.debug("handlePersistenceException(): " + message, pe);

	//clear the castor cache
	CacheManager cm = db.getCacheManager();
	cm.expireCache();

	//set db error message
	//TODO: Update error messaging to better reflect the actual error
	request.setAttribute("error_message", message);
    }


    /**
     * Handles a PersistenceException: Performs any actions that need to happen when 
     * catching a PersistenceException in a dispatcher.
     */
    protected void handlePersistenceException(Database db, PersistenceException pe) {
	handlePersistenceException(db, pe, "");   
    }


    /**
     * Handles a PersistenceException: Performs any actions that need to happen when 
     * catching a PersistenceException in a dispatcher.
     */
    protected void handlePersistenceException(Database db, PersistenceException pe, String message) {

	log.debug("handlePersistenceException(): " + message, pe);   

	//clear the castor cache
	CacheManager cm = db.getCacheManager();
	cm.expireCache();
    }


    /**
     * Handles a PersistenceException: Performs any actions that need to happen when catching a PersistenceException.
     */
    protected void handlePersistenceException(PersistenceException pe) {
	handlePersistenceException(pe, "");   
    }


    /**
     * Handles a PersistenceException: Performs any actions that need to happen when catching a PersistenceException.
     */
    protected void handlePersistenceException(PersistenceException pe, String message) {

	log.debug("handlePersistenceException(): " + message, pe);   

	Database db = null; 

	//clear the castor cache
	try {
	    db = getJDODatabase();

	    CacheManager cm = db.getCacheManager();
	    cm.expireCache();
	    
	} catch (PersistenceException e) {
	    log.debug("handlePersistenceException could not complete", e);

	} finally {
	    closeJDODatabase(db);

	}
    }


    /**
     * get returnPage from the validator and set nextPage forwarding
     */
    public String getReturnPage(HttpServletRequest request, String defaultPage)
	throws AppException {

	String nextPage = getString("returnPage", request);
	request.setAttribute("returnPage", nextPage);
	if (Utils.isEmpty(nextPage)) nextPage = defaultPage;
	return nextPage;
    }


    /**
     * get returnPage from the validator and set nextPage forwarding
     */
    public String getReturnPage(HttpServletRequest request, String defaultPage, Map params)
	throws AppException {

	String nextPage = getString("returnPage", request, params);
	request.setAttribute("returnPage", nextPage);
	if (Utils.isEmpty(nextPage)) nextPage = defaultPage;
	return nextPage;
    }


    /**
     * get nextPage from the validator and set nextPage forwarding
     * @deprecated    Should be setting returnPage parameter and calling getReturnPage(request, defaultPage) instead
     */
    public String getNextPage(HttpServletRequest request, String defaultPage)
	throws AppException {

	String nextPage = getString("nextPage", request);
	if (Utils.isEmpty(nextPage)) nextPage = defaultPage;
	return nextPage;
    }


    /**
     * get start_in from the validator and set nextPage forwarding
     * @deprecated    Should be setting returnPage parameter and calling getReturnPage(request, defaultPage) instead
     */
    public String getStartIn(HttpServletRequest request, String defaultPage)
	throws AppException {

	String nextPage = getString("start_in", request);
	if (Utils.isEmpty(nextPage)) nextPage = defaultPage;
	return nextPage;
    }

    /**
     * Returns a handle on another module
     */
    public static ServletContext getModule(String moduleName) {
	return application.getContext(moduleName);
    }


    /**
     * Returns true if a particular module is deployed within this host
     */
    public static boolean moduleExists(String moduleName) {
	ServletContext tmpl = getModule(moduleName);
	ServletContext root = getModule("/");
	return ( tmpl != null && tmpl != root );
    }


    /** 
     * Returns the context path without requiring the request object.
     */
    public static String getContextPath(ServletContext context) {
	if (context == null) return "/";

	String realPath = context.getRealPath("/");
	log.debug("getContextPath(): realPath: " + realPath);

	String[] pathComponents = realPath.split("/"); 
	log.debug("    pathComponents: " + pathComponents);

	String contextPath      = pathComponents[ pathComponents.length - 1 ];
	log.debug("    contextPath: " + contextPath);

	return "/" + contextPath;
    }


    /** 
     * Returns true if the User-Agent header indicates a mobile browser.
     */
    public static boolean isMobileBrowser(HttpServletRequest request) {

	if (request == null) return false;

	String userAgent = request.getHeader("User-Agent");
	userAgent        = StringUtils.toLowerCase(userAgent);

	Matcher m1       = MOBILE_BROWSERS.matcher(userAgent);

	if (m1.matches()) return true;

	Matcher m2       = MOBILE_BROWSERS_2.matcher( StringUtils.substring(userAgent, 0, 4) );

	return m2.matches();
    }

    static final Pattern MOBILE_BROWSERS   = Pattern.compile(".*(android|avantgo|blackberry|blazer|compal|elaine|fennec|hiptop|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile|o2|opera m(ob|in)i|palm( os)?|p(ixi|re)\\/|plucker|pocket|psp|smartphone|symbian|treo|up\\.(browser|link)|vodafone|wap|windows ce; (iemobile|ppc)|xiino).*");
    static final Pattern MOBILE_BROWSERS_2 = Pattern.compile("1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\\-(n|u)|c55\\/|capi|ccwa|cdm\\-|cell|chtm|cldc|cmd\\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\\-s|devi|dica|dmob|do(c|p)o|ds(12|\\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\\-|_)|g1 u|g560|gene|gf\\-5|g\\-mo|go(\\.w|od)|gr(ad|un)|haie|hcit|hd\\-(m|p|t)|hei\\-|hi(pt|ta)|hp( i|ip)|hs\\-c|ht(c(\\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\\-(20|go|ma)|i230|iac( |\\-|\\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\\/)|klon|kpt |kwc\\-|kyo(c|k)|le(no|xi)|lg( g|\\/(k|l|u)|50|54|e\\-|e\\/|\\-[a-w])|libw|lynx|m1\\-w|m3ga|m50\\/|ma(te|ui|xo)|mc(01|21|ca)|m\\-cr|me(di|rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\\-2|po(ck|rt|se)|prox|psio|pt\\-g|qa\\-a|qc(07|12|21|32|60|\\-[2-7]|i\\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\\-|oo|p\\-)|sdk\\/|se(c(\\-|0|1)|47|mc|nd|ri)|sgh\\-|shar|sie(\\-|m)|sk\\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\\-|v\\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\\-|tdg\\-|tel(i|m)|tim\\-|t\\-mo|to(pl|sh)|ts(70|m\\-|m3|m5)|tx\\-9|up(\\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|xda(\\-|2|g)|yas\\-|your|zeto|zte\\-");

    //SERVLET UTILITY METHODS ------------------------------------------------

    /**
     * Attempts to load an init parameter from the servlet config. If no value is found in
     * the servlet config, it will try the application.
     */
    protected String loadInitParameter(String paramName) {
	
	return loadInitParameter(paramName, "");
    }


    /**
     * Attempts to load an init parameter from the servlet config. If no value is found in
     * the servlet config, it will try the application. If nothing is found in the application
     * context, the default value is returned.
     */
    protected String loadInitParameter(String paramName, String defaultValue) {

	log.debug("Loading init parameter: " + paramName);

        String output = getServletConfig().getInitParameter(paramName);
	log.debug("    value from servlet config: " + output);

        if (Utils.isEmpty(output)) {
	    output = getServletContext().getInitParameter(paramName);
	    log.debug("    value from context config: " + output);
	}

        if (Utils.isEmpty(output)) {
	    output = defaultValue;
	    log.debug("    value from default:        " + output);
	}

        return output;
    }


    /**                                                                                                                
     * Attempts to load an init parameter from the servlet config. If no value is found in                              
     * the servlet config, it will try the application. If nothing is found in the application                              
     * context, the default value is returned.                  
     * 
     * This implementation tries to parse the init parameter as a boolean. If the input cannot be 
     * parsed as a boolean, the default value will be returned.
     */
    protected Boolean loadInitParameter(String paramName, Boolean defaultValue) {

        log.debug("Loading init parameter: " + paramName);

        String param = getServletConfig().getInitParameter(paramName);
        log.debug("    value from servlet config: " + param);

        if (Utils.isEmpty(param)) {
            param = getServletContext().getInitParameter(paramName);
            log.debug("    value from context config: " + param);
        }

	if ("true".equalsIgnoreCase(param)) {
	    return true;

	} else if ("false".equalsIgnoreCase(param)) {
	    return false;

	}	
	
        return defaultValue;
    }


    /**                                                                                                                
     * Attempts to load an init parameter from the servlet config. If no value is found in                              
     * the servlet config, it will try the application. If nothing is found in the application                              
     * context, the default value is returned.                  
     * 
     * This implementation tries to parse the init parameter as an integer. If the input cannot be 
     * parsed as an integer, the default value will be returned.
     */
    protected Integer loadInitParameter(String paramName, Integer defaultValue) {

        log.debug("Loading init parameter: " + paramName);

        String param = getServletConfig().getInitParameter(paramName);
        log.debug("    value from servlet config: " + param);

        if (Utils.isEmpty(param)) {
            param = getServletContext().getInitParameter(paramName);
            log.debug("    value from context config: " + param);
        }

	if (!Utils.isEmpty(param)) {
	    try {
		return convertInteger(param);
	    } catch (AppException ae) {
		//no-op
	    }
	}
	
        return defaultValue;
    }


    //JDO UTILITY METHODS ----------------------------------------------------


    /**
     * Returns a JDO database instance
     */
    public Database getJDODatabase() 
        throws org.exolab.castor.jdo.DatabaseNotFoundException,
               org.exolab.castor.jdo.PersistenceException {
        return jdo.getDatabase();
    }


    /**
     * Closes a JDO database instance
     */
    public static void closeJDODatabase(Database db) {
	try {
	    log.debug("Closing JDO Database");

	    //rollback transaction                                                                                              
	    if (db != null && db.isActive()) {
		log.debug("   -Rollback");
		db.rollback();
	    }

	    if (db != null && !db.isClosed()) {
		log.debug("   -Close");
		db.close();
	    }

	    db = null;

	} catch (Exception fe) {
	    log.debug("", fe);
	}
    }

    /**
     * Clears the JDO cache
     */
    public void clearJDOCache() {
	log.debug("Clearing JDO cache");

	Database db = null;

	try {
	    db = getJDODatabase();

	    CacheManager cm = db.getCacheManager();
	    cm.expireCache();

	} catch (PersistenceException pe) {
	    handlePersistenceException(db, pe, "Unable to clear JDO cache");
	}
    }

    // XML-XSL MARSHALLING METHODS ------------------------------------------------------------------

    /**
     * Marshals an Object to the request attributes as XML
     *
     * @param obj     The mapped Object to be marshalled
     * @param name    The request attribute name the object will be marshalled as
     * @param request The HttpServletRequest to marshal to
     * @param mapping The mapping to use for converting the object to XML
     */
    public static void marshal(Object obj, String name, HttpServletRequest request, Mapping mapping)
        throws java.io.IOException, org.exolab.castor.mapping.MappingException,
               org.exolab.castor.xml.MarshalException, org.exolab.castor.xml.ValidationException {

        if (obj == null) return;

        StringWriter writer = new StringWriter();
        Marshaller m = new Marshaller(writer);
        m.setMapping(mapping);
        m.setRootElement(name);
        m.marshal(obj);

        request.setAttribute(name, writer.toString());
    }


    /**                                                                                                          
     * Marshals an Object to the response as XML                   
     */
    public static void marshal(Object obj, HttpServletResponse response, Mapping mapping)
        throws java.io.IOException, org.exolab.castor.mapping.MappingException,
               org.exolab.castor.xml.XMLException {

        if (obj == null) return;

	response.setContentType("text/xml");
        PrintWriter writer = response.getWriter();
        Marshaller m = new Marshaller(writer);
        m.setMapping(mapping);
        m.marshal(obj);

        writer.flush();
        writer.close();
    }


    /**
     * Marshals a plain-text String to the response.
     */
    public static void marshal(String text, HttpServletResponse response) {
	marshal(text, response, "text/plain");
    }


    /**
     * Marshals a plain-text String to the response.
     */
    public static void marshal(String text, HttpServletResponse response, String contentType) {

	if (Utils.isEmpty(contentType)) contentType = "text/plain";
        response.setContentType(contentType);

        PrintWriter writer = null;

        try {
            writer = response.getWriter();
            writer.write(text);
            writer.flush();

        } catch (IOException ioe) {
            //no-op                                                 

        } finally {
            closeStream(writer);
        }
    }


    /**
     * Marshals a StringBuffer to the response.
     */
    public static void marshal(StringBuffer text, HttpServletResponse response) {
	if (text != null) marshal(text.toString(), response);
    }


    /**
     * Marshalls an Object into XML as a String
     */
    public static String marshal(Object obj) 
	throws java.io.IOException, org.exolab.castor.mapping.MappingException, org.exolab.castor.xml.XMLException {

	if (obj == null) return "";

	// create a Writer to marshal to
	StringWriter writer = new StringWriter();

	// create a new Marshaller from the XML context
	Marshaller marshaller = XML_CONTEXT.createMarshaller();
	marshaller.setWriter(writer);
	
	// marshal the object
	marshaller.marshal(obj);

	return writer.toString();
    }

    
    /**
     * Marshalls an Object into XML as a String
     */
    public static String marshal(Object obj, Mapping mapping) 
	throws java.io.IOException, org.exolab.castor.mapping.MappingException, org.exolab.castor.xml.XMLException {

	if (obj == null) return "";

	StringWriter writer = new StringWriter();
	Marshaller m = new Marshaller(writer);
	m.setMapping(mapping);
	m.marshal(obj);

	return writer.toString();
    }

    
    /**
     * Marshalls an Object into XML as a String
     */
    public static String marshal(Object obj, Mapping mapping, String stylesheet) 
	throws java.io.IOException, org.exolab.castor.mapping.MappingException, org.exolab.castor.xml.XMLException {

	if (obj == null) return "";

	StringWriter writer = new StringWriter();
	Marshaller m = new Marshaller(writer);
	m.setMapping(mapping);

	m.addProcessingInstruction("xml-stylesheet", "href=\"" + stylesheet + "\" type=\"text/xsl\"");	

	m.marshal(obj);

	return writer.toString();
    }

    
    /**
     * Unmarshals XML into an Object. Since no mapping is provided this method attempts to load generic XML
     */
    public static Object unmarshal(String xml, Class targetClass) 
	throws org.exolab.castor.mapping.MappingException, org.exolab.castor.xml.XMLException {

	// Create a Reader to the String to unmarshal from
	Reader reader = new StringReader(xml);

	// Unmarshal the object
	Object output = Unmarshaller.unmarshal(targetClass, reader);

	return output;
    }


    /**
     * Unmarshals XML into an Object
     */
    public static Object unmarshal(String xml, Class targetClass, Mapping mapping) 
	throws org.exolab.castor.mapping.MappingException, org.exolab.castor.xml.XMLException {

	// Create a Reader to the String to unmarshal from
	Reader reader = new StringReader(xml);

	// Create a new Unmarshaller
	Unmarshaller unmarshaller = new Unmarshaller(targetClass);
	unmarshaller.setMapping(mapping);

	// Unmarshal the object
	Object output = unmarshaller.unmarshal(reader);

	return output;
    }


    /**
     * Unmarshals XML at the specified URL into an Object. 
     */
    public static Object unmarshal(URL url, Class targetClass, Mapping mapping) 
	throws org.exolab.castor.mapping.MappingException, org.exolab.castor.xml.XMLException, IOException {

	// Create a Reader to the String to unmarshal from
	Reader reader = new InputStreamReader(url.openStream());

	// Create a new Unmarshaller
	Unmarshaller unmarshaller = new Unmarshaller(targetClass);
	unmarshaller.setMapping(mapping);

	// Unmarshal the object
	Object output = unmarshaller.unmarshal(reader);

	return output;
    }


    /**
     * Converts an XML doc to a String using the supplied XSL
     *
     * @todo   Create a version of this method that streams the results directly to the ServletResponse
     */
    public String transformXml(String xmlString, String stylesheetLocation) {

        StringWriter out    = new StringWriter();

        try {
            TransformerFactory tFactory = TransformerFactory.newInstance();

            // Get the XML input document and the stylesheet.                                               
            Source xslSource = new StreamSource( application.getResource(stylesheetLocation).toString() );

	    log.debug("xslSource: " + xslSource);

            // Generate the transformer.                                                                                
            Transformer transformer = tFactory.newTransformer(xslSource);

            // Perform the transformation, sending the output to the response.                                       
            transformer.transform(new StreamSource(new StringReader(xmlString)),
                                  new StreamResult(out) );

            return out.toString();

        } catch (Exception e) {
            log.debug("", e);

        } finally {
            closeStream(out);
        }

        return "";
	
    }


    /**
     * Formats a String as HTML: 
     * <LI>Linking http and https URLs                                                                                             
     * <LI>Adding line breaks ( &lt;BR&gt; )                                                        
     */
    public static String formatHTML(String input) {
        return StringUtils.formatHTML(input);
    }


    //ENCRYPT / DECRYPT METHODS ------------------------------------

    public static String encrypt(String clearText, Cipher ecipher) 
	throws java.io.UnsupportedEncodingException, java.security.GeneralSecurityException {

	byte[] utf8 = clearText.getBytes("UTF8"); // Encode the string into bytes using utf-8                                   
	byte[] enc  = ecipher.doFinal(utf8); // Encrypt                                                                      
	String cypherText = com.Ostermiller.util.Base64.encodeToString(enc); // Encode bytes to base64 to get a string

	return cypherText;
    }


    public static String decrypt(String cipherText, Cipher dcipher) 
	throws java.io.UnsupportedEncodingException, java.security.GeneralSecurityException {

	byte[] dec  = com.Ostermiller.util.Base64.decodeToBytes( cipherText, "UTF8" ); // Decode base64 to get bytes            
	byte[] utf8 = dcipher.doFinal(dec); // Decrypt                                                                          
	String clearText = new String(utf8, "UTF8"); // Decode using utf-8

	return clearText;
    }


    //UTILITY METHODS ----------------------------------------------

    /**
     * Closes a Stream
     */
    public static void closeStream(Closeable s) {
	try {
	    if (s != null) s.close();
	} catch (IOException ioe) {
	    log.debug("", ioe);
	}
    }


    /**
     * Returns the real path to this servlet
     */
    public static String getLocation() {
	return location;
    }


    /**
     * Returns the server name portion of the request URL. 
     * For example, if the request URL is: 
     * <BR><BR>
     * http://www.codemagi.com/servlet?param1=value1
     * <BR><BR>  
     * Then this method will return: 
     * <BR><BR>  
     * http://www.codemagi.com
     */
    public static String getServerName(HttpServletRequest request) {
	StringBuffer url  = request.getRequestURL();
	return url.substring(0, url.indexOf("/", url.indexOf("//") +2));
    }


    /**
     * Returns true if this request is secure, either by the <code>request.isSecure()</code> method or 
     * by checking for the presence of a request header.
     */
    private boolean isSecure(HttpServletRequest request) {
	
	log.debug("BaseServlet.isSecure() called...");

	//first try the java internal method
	if (request.isSecure()) {
	    log.debug("Connection secure according to ServletRequest");
	    return true;
	}

	//if there is an ssl accelerator in use, check for the existence of 
	//headers indicating that the accelerator has processed the request
        boolean secure = false;
        if ( !Utils.isEmpty(httpsHeaderName) ) {

            String headerValue = request.getHeader(httpsHeaderName);
	    log.debug("headerValue      = " + headerValue);
	    log.debug("httpsHeaderValue = " + httpsHeaderValue);

	    if (!Utils.isEmpty(headerValue)) {
		log.debug("EQUAL: " + headerValue.contains(httpsHeaderValue));
		secure = headerValue.contains(httpsHeaderValue); 
	    }
        }

        log.debug("Connection secure = " + secure);
        return secure;
    }


    /**
     * Forces a request to be secure (SSL) by redirecting non-secure URLs to https
     */
    protected void forceSecure(HttpServletRequest request, HttpServletResponse response) 
	throws AppException, SecureException {

	if (!isSecure(request) ) {

	    String vQueryString = request.getQueryString();
	    String vServer      = request.getServerName();

	    String vPageUrl     = (String)request.getAttribute("javax.servlet.forward.request_uri");
	    if (Utils.isEmpty(vPageUrl)) vPageUrl = request.getRequestURI();

	    StringBuilder vRedirect = new StringBuilder();
	    vRedirect.append("https://");
	    if (httpsPort == null || httpsPort.trim().length() == 0 || httpsPort.equals(STD_HTTPS_PORT)) {
		vRedirect.append(vServer).append(vPageUrl);

	    } else {
		vRedirect.append(vServer).append(":").append(httpsPort).append(vPageUrl);

	    }

	    if (vQueryString != null) {
		vRedirect.append("?");
		vRedirect.append(vQueryString);
	    }

	    log.debug("attempting to redirect to: " + vRedirect);

	    try {
		response.sendRedirect(vRedirect.toString());
		throw new SecureException(MSG_REDIRECTED_SECURE);

	    } catch (IOException ioe) {
		log.error("", ioe);

		throw new AppException(MSG_UNABLE_TO_SECURE);
	    }
	}

    }


    /**
     * Forces a request to be non-secure by redirecting https URLs to http
     */
    protected void forceNonSecure(HttpServletRequest request, HttpServletResponse response) 
	throws AppException, SecureException {

	if (isSecure(request) ) {

	    String vQueryString = request.getQueryString();
	    String vServer      = request.getServerName();

	    String vPageUrl     = (String)request.getAttribute("javax.servlet.forward.request_uri");
	    if (Utils.isEmpty(vPageUrl)) vPageUrl = request.getRequestURI();

	    StringBuilder vRedirect = new StringBuilder();
	    vRedirect.append("http://");
	    if (httpPort == null || httpPort.trim().length() == 0 || httpPort.equals(STD_HTTP_PORT)) {
		vRedirect.append(vServer).append(vPageUrl);

	    } else {
		vRedirect.append(vServer).append(":").append(httpPort).append(vPageUrl);

	    }

	    if (vQueryString != null) {
		vRedirect.append("?");
		vRedirect.append(vQueryString);
	    }

	    log.debug("attempting to redirect to: " + vRedirect);

	    try {
		response.sendRedirect(vRedirect.toString());

		throw new SecureException(MSG_REDIRECTED_SECURE);

	    } catch (IOException ioe) {
		log.error("", ioe);

		throw new AppException(MSG_UNABLE_TO_SECURE);
	    }
	}

    }


    //ACCESS CHECKING METHODS ---------------------------------------

    /** 
     * Returns the IUser making the request without doing any login or access checks.
     */
    public IUser getUser(HttpServletRequest request) {

	IUser output = null;

	//attempt to get user first using a LoginClient
	ILoginClient loginClient = (ILoginClient)application.getAttribute("loginClient");
        if (loginClient != null) {
	    try {
		output = loginClient.getUser(request, false);
	    } catch (LoginException le) {
		//no-op
	    }

	} else {
	    //fallback to the traditional session based login
	    output = (IUser)request.getSession().getAttribute("user");
	}

	return output;	        
    }

    /**
     * Checks whether a session has been properly authenticated, throws an exception otherwise.
     *
     * @param session    HttpSession to check for authentication
     * @deprecated For various compatibility reasons, it is better to use checkLogin(HttpServletRequest request)
     */
    protected IUser checkLogin(HttpSession session) throws LoginException {
	IUser user = (IUser)session.getAttribute("user");
        if (user == null || !user.isAuthenticated()) {
            log.debug("NOT LOGGED IN! USER: " + user);

            throw new LoginException("");
        }
	
	return user;
    }


    /**
     * Checks whether a session has been properly authenticated, throws an exception otherwise.
     */
    protected IUser checkLogin(HttpServletRequest request) throws LoginException {
	return checkLogin(request, null);
    }


    /**
     * Checks whether a session has been properly authenticated, throws an exception otherwise. 
     *
     * @param session   HttpServletRequest to check for login
     * @param nextPage  The next page to forward to after login is completed
     */
    protected IUser checkLogin(HttpServletRequest request, String nextPage) throws LoginException {
	
	//attempt to login first using a LoginClient
	ILoginClient loginClient = (ILoginClient)application.getAttribute("loginClient");
        if (loginClient != null) {
	    try {
		return loginClient.getUser(request, true);
	    } catch (LoginException le) {
		request.setAttribute("nextPage", nextPage);
		throw le;
	    }

	} else {
	    //fallback to the traditional session based login
	    HttpSession session = request.getSession();

	    IUser user = (IUser)session.getAttribute("user");
	    if (user == null || !user.isAuthenticated()) {
		log.debug("NOT LOGGED IN!");

		request.setAttribute("nextPage", nextPage);
		throw new LoginException("");
	    }

	    return user;
	}
    }


    /**
     * Checks whether a user is logged in and has the appropriate permission level to perform an action
     *
     * @deprecated For various compatibility reasons, it is better to use checkPermission(HttpServletRequest, String) instead
     */
    protected IUser checkPermission(HttpSession session, String permission) 
        throws LoginException, AccessException {

        IUser user = checkLogin(session);

        if (!user.isInGroup(permission)) {
            log.debug("ACCESS DENIED");

            throw new AccessException("");
        }

	return user;
    }


    /**
     * Checks whether a user is logged in and has the appropriate permission level to perform an action.
     *
     * @exception com.codemagi.login.AccessException if the user is not in at least one of the passed groups
     * @deprecated For various compatibility reasons, it is better to use checkPermission(HttpServletRequest, String[]) instead
     */
    protected IUser checkPermission(HttpSession session, String[] permissions) 
        throws LoginException, AccessException {

        IUser user = checkLogin(session);
	
	for (int i = 0; i < permissions.length; i++) {
	    if (user.isInGroup(permissions[i])) return user;
	}

	log.debug("ACCESS DENIED");
	throw new AccessException("");
    }


    /**
     * Checks whether a user is logged in and has the appropriate permission level to perform an action
     */
    protected IUser checkPermission(HttpServletRequest request, String permission) 
        throws LoginException, AccessException {

        IUser user = checkLogin(request);

        if (!user.isInGroup(permission)) {
            log.debug("ACCESS DENIED");

            throw new AccessException("");
        }

	return user;
    }


    /**
     * Checks whether a user is logged in and has the appropriate permission level to perform an action.
     *
     * @exception com.codemagi.login.AccessException if the user is not in at least one of the passed groups
     */
    protected IUser checkPermission(HttpServletRequest request, String[] permissions) 
        throws LoginException, AccessException {

        IUser user = checkLogin(request);

	for (int i = 0; i < permissions.length; i++) {
	    if (user.isInGroup(permissions[i])) return user;
	}

	log.debug("ACCESS DENIED");
	throw new AccessException("");
    }


    //INPUT VALIDATION ---------------------------------------------

    /**
     * Adds an input validator. Note that this method only adds, it does not replace existing localValidators.
     */
    protected void addValidator(String acton, String param, IValidator validator) {
	//sanity check
	if (validator == null) return;

	List<IValidator> checks = (List<IValidator>)validators.get(acton, param);
	if (checks == null) checks = new ArrayList<IValidator>();

	checks.add(validator);

	validators.put(acton, param, checks);
    }


    /**
     * Adds an array of input validators. Note that this method only adds, it does not replace existing localValidators.
     */
    protected void addValidator(String acton, String param, IValidator[] validatorArray) {
	for (int i = 0; i < validatorArray.length; i++) {
	    addValidator(acton, param, validatorArray[i]);
	}
    }


    /**
     * Removes all validators for a particular acton and param.
     */
    protected void clearValidators(String acton, String param) {
	validators.remove(acton, param);
    }


    /**
     * Validates request parameters.
     */
    protected Object validate(String paramName, HttpServletRequest request) throws AppException {

	//get the param to validate
	String acton = request.getParameter("acton");

	//get the validation checks to run                                                    
        List<IValidator> checks = (List<IValidator>)validators.get(acton, paramName);

	return validate(paramName, request, checks);
    }

    /**
     * Validates a request parameter against the one passed validator
     */
    protected Object validate(String paramName, HttpServletRequest request, IValidator check) throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(check);

	return validate(paramName, request, localValidators);
    }

    /**
     * Validates request parameters.
     */
    protected Object validate(String paramName, Integer suffix, HttpServletRequest request) throws AppException {

	//get the param to validate 
	String acton = request.getParameter("acton");

	//get the validation checks to run
	List<IValidator> checks = (List<IValidator>)validators.get(acton, paramName);

	return validate(paramName + suffix, request, checks);
    }

    /**
     * Validates request parameters.
     */
    protected Object validate(String paramName, HttpServletRequest request, List<IValidator> checks) throws AppException {

	//get the param to validate 
	String[] values = request.getParameterValues(paramName);
	Object param    = (values != null && values.length > 1) ? Arrays.asList(values) : request.getParameter(paramName) ;
	//	Object param = request.getParameter(paramName);

	//the value to be returned
	Object output = param;

	log.debug("validating: " + paramName);
	if (checks != null) {

	    //run the checks
	    try {
		for (IValidator check : checks) {
		    log.debug("Running validation check: " + check);
		    output = check.validate(output, request);
		    if (("password".equals(paramName)) ||
			("currentPass".equals(paramName)) ||
			("pass1".equals(paramName)) ||
			("pass2".equals(paramName))) {
			log.debug("    Validation for " + paramName + " passed");
		    } else {
			log.debug("    Validation passed: " + output);
		    }
		}
	    } catch (AppException e) {
		log.debug("    VALIDATION FAILED! " + e.getMessage());
		if (("password".equals(paramName)) ||
		    ("pass1".equals(paramName)) ||
		    ("pass2".equals(paramName))) {
		    log.debug("               PARAMETER: " + paramName);
		} else {
		    log.debug("               OUTPUT: " + output);
		}

		request.setAttribute("error_" + paramName + "_error", "true");

		String validationError = (String)request.getAttribute("validation_error");
		if ( Utils.isEmpty(validationError) ) request.setAttribute("validation_error", e.getMessage());

		output = null;
	    }
	}
	    
	return output;
    }



    /**
     * Validates request parameters.
     */
    protected Object validate(String paramName, HttpServletRequest request, Map params) throws AppException {

	//get the param to validate 
	String acton = (String)params.get("acton");

	//get the validation checks to run
	List<IValidator> checks = (List<IValidator>)validators.get(acton, paramName);

	return validate(paramName, request, params, checks);
    }

    /**
     * Validates request parameters.
     */
    protected Object validate(String paramName, Integer suffix, HttpServletRequest request, Map params) throws AppException {

	//get the param to validate 
	String acton = (String)params.get("acton");

	//get the validation checks to run
	List<IValidator> checks = (List<IValidator>)validators.get(acton, paramName);

	return validate(paramName + suffix, request, params, checks);
    }

    /**
     * Validates a request parameter in the passed Map against the passed List of IValidators
     */
    protected Object validate(String paramName, HttpServletRequest request, Map params, List<IValidator> checks) 
	throws AppException {

	//get the param to validate 
	Object param = params.get(paramName);

	//the value to be returned
	Object output = param;

	log.debug("validating: " + paramName);
	if (checks != null) {

	    //run the checks
	    try {
		for (IValidator check : checks) {
		    log.debug("Running validation check: " + check);
		    output = check.validate(output, request);
		    if (("password".equals(paramName)) ||
			("pass1".equals(paramName)) ||
			("pass2".equals(paramName))) {
			log.debug("    Validation for " + paramName + " passed");
		    } else {
			log.debug("    Validation passed: " + output);
		    }
		}
	    } catch (AppException e) {
		log.debug("    VALIDATION FAILED! " + e.getMessage());
		if (("password".equals(paramName)) ||
		    ("pass1".equals(paramName)) ||
		    ("pass2".equals(paramName))) {
		    log.debug("               PARAMETER: " + paramName);
		} else {
		    log.debug("               OUTPUT: " + output);
		}

		request.setAttribute("error_" + paramName + "_error", "true");

		String validationError = (String)request.getAttribute("validation_error");
		if ( Utils.isEmpty(validationError) ) request.setAttribute("validation_error", e.getMessage());

		output = null;
	    }
	}
	    
	return output;
    }

    /**
     * Gets a validated String from the request parameters
     */
    protected String getString(String param, HttpServletRequest request) throws AppException {
	return (String)validate(param, request);
    }

    /**
     * Gets a validated String from the passed Map of parameters
     */
    protected String getString(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (String)validate(param, suffix, request);
    }

    /**
     * Gets a validated String from the passed Map of parameters
     */
    protected String getString(String param, HttpServletRequest request, Map params) throws AppException {
	return (String)validate(param, request, params);
    }

    /**
     * Gets a validated String from the passed Map of parameters
     */
    protected String getString(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (String)validate(param, suffix, request, params);
    }

    /**
     * Gets a String from the passed Map of parameters and validates it against the passed Validator
     */
    protected String getString(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getString(param, request, params, localValidators);
    }

    /**
     * Gets a String from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected String getString(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (String)validate(param, request, params, validators);
    }


    /**
     * Gets a validated Date from the request parameters
     */
    protected Date getDate(String param, HttpServletRequest request) throws AppException {
	return (Date)validate(param, request);
    }

    /**
     * Gets a validated Date from the passed Map of parameters
     */
    protected Date getDate(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (Date)validate(param, suffix, request);
    }

    protected Date getDate(String param, HttpServletRequest request, Map params) throws AppException {
	return (Date)validate(param, request, params);
    }

    /**
     * Gets a validated Date from the passed Map of parameters
     */
    protected Date getDate(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (Date)validate(param, suffix, request, params);
    }

    /**
     * Gets a Date from the passed Map of parameters and validates it against the passed Validator
     */
    protected Date getDate(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getDate(param, request, params, localValidators);
    }

    /**
     * Gets a Date from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected Date getDate(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (Date)validate(param, request, params, validators);
    }

    /**
     * Gets a validated Integer from the request parameters
     */
    protected Integer getInteger(String param, HttpServletRequest request) throws AppException {
	return (Integer)validate(param, request);
    }

    /**
     * Gets a validated Integer from the passed Map of parameters
     */
    protected Integer getInteger(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (Integer)validate(param, suffix, request);
    }

    protected Integer getInteger(String param, HttpServletRequest request, Map params) throws AppException {
	return (Integer)validate(param, request, params);
    }

    /**
     * Gets a validated Integer from the passed Map of parameters
     */
    protected Integer getInteger(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (Integer)validate(param, suffix, request, params);
    }

    /**
     * Gets a Integer from the passed Map of parameters and validates it against the passed Validator
     */
    protected Integer getInteger(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getInteger(param, request, params, localValidators);
    }

    /**
     * Gets a Integer from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected Integer getInteger(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (Integer)validate(param, request, params, validators);
    }

    /**
     * Gets a validated Long from the request parameters
     */
    protected Long getLong(String param, HttpServletRequest request) throws AppException {
	return (Long)validate(param, request);
    }

    /**
     * Gets a validated Long from the passed Map of parameters
     */
    protected Long getLong(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (Long)validate(param, suffix, request);
    }

    protected Long getLong(String param, HttpServletRequest request, Map params) throws AppException {
	return (Long)validate(param, request, params);
    }

    /**
     * Gets a validated Long from the passed Map of parameters
     */
    protected Long getLong(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (Long)validate(param, suffix, request, params);
    }

    /**
     * Gets a Long from the passed Map of parameters and validates it against the passed Validator
     */
    protected Long getLong(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getLong(param, request, params, localValidators);
    }

    /**
     * Gets a Long from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected Long getLong(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (Long)validate(param, request, params, validators);
    }

    /**
     * Gets a validated Float from the request parameters
     */
    protected Float getFloat(String param, HttpServletRequest request) throws AppException {
	return (Float)validate(param, request);
    }

    /**
     * Gets a validated Float from the passed Map of parameters
     */
    protected Float getFloat(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (Float)validate(param, suffix, request);
    }
    
    protected Float getFloat(String param, HttpServletRequest request, Map params) throws AppException {
	return (Float)validate(param, request, params);
    }

    /**
     * Gets a validated Float from the passed Map of parameters
     */
    protected Float getFloat(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (Float)validate(param, suffix, request, params);
    }

    /**
     * Gets a Float from the passed Map of parameters and validates it against the passed Validator
     */
    protected Float getFloat(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getFloat(param, request, params, localValidators);
    }

    /**
     * Gets a Float from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected Float getFloat(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (Float)validate(param, request, params, validators);
    }

    /**
     * Gets a validated Double from the request parameters
     */
    protected Double getDouble(String param, HttpServletRequest request) throws AppException {
	return (Double)validate(param, request);
    }

    /**
     * Gets a validated Double from the passed Map of parameters
     */
    protected Double getDouble(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (Double)validate(param, suffix, request);
    }
    
    protected Double getDouble(String param, HttpServletRequest request, Map params) throws AppException {
	return (Double)validate(param, request, params);
    }

    /**
     * Gets a validated Double from the passed Map of parameters
     */
    protected Double getDouble(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (Double)validate(param, suffix, request, params);
    }

    /**
     * Gets a Double from the passed Map of parameters and validates it against the passed Validator
     */
    protected Double getDouble(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getDouble(param, request, params, localValidators);
    }

    /**
     * Gets a Double from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected Double getDouble(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (Double)validate(param, request, params, validators);
    }

    /**
     * Gets a validated Boolean from the request parameters
     */
    protected Boolean getBoolean(String param, HttpServletRequest request) throws AppException {
	return (Boolean)validate(param, request);
    }

    /**
     * Gets a validated Boolean from the passed Map of parameters
     */
    protected Boolean getBoolean(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (Boolean)validate(param, suffix, request);
    }
    
    protected Boolean getBoolean(String param, HttpServletRequest request, Map params) throws AppException {
	return (Boolean)validate(param, request, params);
    }

    /**
     * Gets a validated Boolean from the passed Map of parameters
     */
    protected Boolean getBoolean(String param, Integer suffix, HttpServletRequest request, Map params) throws AppException {
	return (Boolean)validate(param, suffix, request, params);
    }

    /**
     * Gets a Boolean from the passed Map of parameters and validates it against the passed Validator
     */
    protected Boolean getBoolean(String param, HttpServletRequest request, Map params, IValidator validator) 
	throws AppException {

	List<IValidator> localValidators = new ArrayList<IValidator>(1);
	localValidators.add(validator);

	return getBoolean(param, request, params, localValidators);
    }

    /**
     * Gets a Boolean from the passed Map of parameters and validates it against the passed array of IValidators
     */
    protected Boolean getBoolean(String param, HttpServletRequest request, Map params, List<IValidator> validators)
	throws AppException {

	return (Boolean)validate(param, request, params, validators);
    }

    /**
     * Gets a validated Set from the request parameters
     */
    protected Set getSet(String param, HttpServletRequest request) throws AppException {
	return (Set)validate(param, request);
    }

    /**
     * Gets a validated Set from the passed Map of parameters
     */
    protected Set getSet(String param, HttpServletRequest request, Map params) throws AppException {
        return (Set)validate(param, request, params);
    }

    /**
     * Gets a validated List from the request parameters
     */
    protected List getList(String param, HttpServletRequest request) throws AppException {
	return (List)validate(param, request);
    }

    /**
     * Gets a validated List from the passed Map of parameters
     */
    protected List getList(String param, HttpServletRequest request, Map params) throws AppException {
        return (List)validate(param, request, params);
    }

    /**
     * Gets a validated List from the passed parameter name and suffix
     */
    protected List getList(String param, Integer suffix, HttpServletRequest request) throws AppException {
	return (List)validate(param, suffix, request);
    }

    /**
     * If there were validation errors, this method will throw an AppException containing the error message.
     */
    protected void checkValidation(ServletRequest request) throws AppException {
	String error = (String)request.getAttribute("validation_error");

	log.debug("checkValidation() ERROR: " + error);

	if (!Utils.isEmpty(error)) throw new AppException(error);
    }

    //FORM CHECKING METHODS ----------------------------------------


    public static void checkRequired(String[] requiredFields) throws AppException {
        if (Utils.isEmpty(requiredFields)) {
            throw new AppException(MSG_MISSING_REQUIRED);
        }
    }


    public static void checkRequired(String requiredField) throws AppException {
        if (Utils.isEmpty(requiredField)) {
            throw new AppException(MSG_MISSING_REQUIRED);
        }
    }


    /**
     * Checks that the input List is not null, that it contains at least one element, and that 
     * all elements are not null and not "".
     *
     * @throws AppException   If any of the above conditions are not satisfied
     */
    public static void checkRequired(List requiredFields) throws AppException {
	if (requiredFields == null || requiredFields.size() < 1) throw new AppException(MSG_MISSING_REQUIRED);

	Iterator i = requiredFields.iterator();
	while (i.hasNext()) {
	    checkRequired(i.next());
	}
    }


    public static void checkRequired(Object[] requiredFields) throws AppException {
	for (int i = 0; i < requiredFields.length; i++) {
	    Object field = requiredFields[i];

	    if (field == null || 
		(field instanceof String && Utils.isEmpty((String)field) ) ||  
		(field instanceof Collection && ((Collection)field).isEmpty()) 
		) {
		log.debug("ERROR: Missing required item #" + i);
		throw new AppException(MSG_MISSING_REQUIRED);
	    }
	}
    }


    public static void checkRequired(Object requiredField) throws AppException {
	Object[] required = { requiredField };

	checkRequired(required);
    }


    public static void checkRequired(String requiredFieldName, HttpServletRequest request) throws AppException {
	String[] required = { requiredFieldName };

	checkRequired(required, request);
    }

    
    public static void checkRequired(String[] requiredFieldNames, HttpServletRequest request) throws AppException {
	boolean isMissing = false;
	for (int i = 0; i < requiredFieldNames.length; i++) {
	    String fieldName = requiredFieldNames[i];
	    if (Utils.isEmpty(request.getParameter( fieldName )) ) {
		isMissing = true;
		request.setAttribute("error_" + fieldName + "_error", "true");
		log.debug("Missing required field: " + fieldName);
	    }
	}

	if (isMissing) throw new AppException(MSG_MISSING_REQUIRED);
    }


    /**
     * Takes a List of STRINGS
     */
    public static void checkRequired(List<String> requiredFieldNames, HttpServletRequest request) throws AppException {
	boolean isMissing = false;

	Iterator i = requiredFieldNames.iterator();
	while (i.hasNext()) {
	    String fieldName = (String)i.next();
	    if (Utils.isEmpty(request.getParameter( fieldName )) ) {
		isMissing = true;
		request.setAttribute("error_" + fieldName + "_error", "true");
	    }
	}

	if (isMissing) throw new AppException(MSG_MISSING_REQUIRED);
    }


    /**
     * Checks that a String is a valid integer. Uses Integer.parseInt() to check.
     */
    public static void checkValidIntegers(String ints) throws AppException {
        String[] required = {ints};

        checkValidIntegers(required);
    }


    public static void checkValidIntegers(String[] ints) throws AppException {

        try {
            for (int i = 0; i < ints.length; i++) {

                Integer.parseInt(ints[i]);

            }
        } catch (NumberFormatException nfe) {
            throw new AppException(MSG_INVALID_INTEGER);
        }
    }


    /**
     * Converts a request parameter to an Integer.
     *
     * @param name    The name of the request parameter to convert
     * @param request The request to get the String from
     * @exception AppException  If the String cannot be parsed
     */
    public static String checkLength(String name, HttpServletRequest request, int maxLength) throws AppException {
        String input = request.getParameter(name);
        if (Utils.isEmpty(input)) return "";

        input = StringUtils.trim(input);
        if (input.length() > maxLength) {
            request.setAttribute("error_" + name + "_error", "true");
            throw new AppException(MSG_STRING_LENGTH_ERROR + maxLength + ".");
        }
        return input;
    }

    /**
     * Checks that the request parameters include the unique token in the user's session to protect against CSRF.
     */
    public static void checkToken(HttpServletRequest request) throws AccessException {
	
	HttpSession session = request.getSession();

	String sessionToken = (String)session.getAttribute("token");
	String requestToken = request.getParameter("token");

	log.debug("Tokens: session: " + sessionToken + " request: " + requestToken);

	if (Utils.isEmpty(sessionToken) ||
	    Utils.isEmpty(requestToken) ||
	    !sessionToken.equals(requestToken) ) throw new AccessException(MSG_MISSING_TOKEN);
    }

    /**
     * Checks that the request parameters include the unique token in the user's session to protect against CSRF.
     */
    public static void checkToken(HttpServletRequest request, Map params) throws AccessException {
	
	HttpSession session = request.getSession();

	String sessionToken = (String)session.getAttribute("token");
	String requestToken = (String)params.get("token");

	log.debug("Tokens: session: " + sessionToken + " request: " + requestToken);

	if (Utils.isEmpty(sessionToken) ||
	    Utils.isEmpty(requestToken) ||
	    !sessionToken.equals(requestToken) ) throw new AccessException(MSG_MISSING_TOKEN);
    }

    /**
     * Converts a String to an Integer. Removes commas, but leaves decimals unchanged.
     *
     * @exception AppException  If the input is null, empty, or cannot be parsed as an Integer
     */
    public static Integer convertInteger(String input) throws AppException {
	input = StringUtils.replace(input, ",", "");
	input = StringUtils.trim(input);
        try {
            return new Integer(input);
        } catch (Exception e) {
            throw new AppException(MSG_INVALID_INTEGER);
        }
    }


    /**
     * Converts a String to an Integer.
     *
     * @param input      String to convert
     * @param allowNull  True if nulls should be allowed, if false, null input will throw an exception
     * @return Integer   The input String parsed to an Integer
     * @exception AppException  If the String cannot be parsed
     */
    public static Integer convertInteger(String input, boolean allowNulls) throws AppException {
        if (allowNulls && Utils.isEmpty(input)) return null;

	return convertInteger(input);
    }


    /**
     * Converts a request parameter to an Integer. 
     * 
     * Note that this version of the method will return null if the request parameter is null.
     *
     * @param name    The name of the request parameter to convert
     * @param request The request to get the String from 
     * @exception AppException  If the String cannot be parsed
     */
    public static Integer convertInteger(String name, HttpServletRequest request) throws AppException {
	return convertInteger(name, request, true);
    }


    /**
     * Converts a request parameter to an Integer. 
     *
     * @param name        The name of the request parameter to convert
     * @param request     The request to get the String from 
     * @param allowNulls  True if nulls should be allowed, if false, null input will throw an exception
     * @exception AppException  If the String cannot be parsed
     */
    public static Integer convertInteger(String name, HttpServletRequest request, boolean allowNulls) throws AppException {
	String input = request.getParameter(name);
	if (allowNulls && Utils.isEmpty(input)) return null;

	input = StringUtils.replace(input, ",", "");
	input = StringUtils.trim(input);
        try {
            return new Integer(input);
        } catch (Exception e) {
	    request.setAttribute("error_" + name + "_error", "true");
            throw new AppException(MSG_INVALID_INTEGER);
        }
    }


    /**
     * Converts a List of Strings into a List of Integers. By default, nulls are not allowed.
     * 
     * This method will attempt to return a List of the same sub type as the list originally passed.
     * If it is unable to return the same type, an ArrayList will be returned. 
     */
    public static List convertInteger(List input) throws AppException {
	List output = null;
	try {
	    output = (List)(input.getClass().newInstance());
	} catch (Exception e) {
	    log.debug("", e);
	    output = new ArrayList();
	}
	Iterator i = input.iterator();
	while (i.hasNext()) {
	    output.add( convertInteger( (String)i.next() ) );
	}
	return output;
    }

    /**
     * Converts a String to a Long. Removes commas, but leaves decimals unchanged.
     *
     * @exception AppException  If the input is null, empty, or cannot be parsed as a Long
     */
    public static Long convertLong(String input) throws AppException {
	input = StringUtils.replace(input, ",", "");
	input = StringUtils.trim(input);
        try {
            return new Long(input);
        } catch (Exception e) {
            throw new AppException(MSG_INVALID_LONG);
        }
    }


    /**
     * Converts a String to a Long.
     *
     * @param input      String to convert
     * @param allowNull  True if nulls should be allowed, if false, null input will throw an exception
     * @return Long      The input String parsed to a Long
     * @exception AppException  If the String cannot be parsed
     */
    public static Long convertLong(String input, boolean allowNulls) throws AppException {
        if (allowNulls && Utils.isEmpty(input)) return null;

	return convertLong(input);
    }


    /**
     * Converts a String to an Float.
     *
     * @exception AppException  If the String cannot be parsed. Removes commas, but leaves decimals unchanged.
     */
    public static Float convertFloat(String input) throws AppException {
	input = StringUtils.replace(input, ",", "");
	input = StringUtils.trim(input);
        try {
            return new Float(input);
        } catch (Exception e) {
            throw new AppException(MSG_INVALID_FLOAT);
        }
    }


    /**
     * Converts a request parameter to an Float.
     *
     * @param name    The name of the request parameter to convert       
     * @param request The request to get the String from
     * @exception AppException  If the String cannot be parsed. Removes commas, but leaves decimals unchanged.
     */
    public static Float convertFloat(String name, HttpServletRequest request) throws AppException {
	String input = request.getParameter(name);
	if (Utils.isEmpty(input)) return null;

	input = StringUtils.replace(input, ",", "");
	input = StringUtils.trim(input);
        try {
            return new Float(input);
        } catch (Exception e) {
	    request.setAttribute("error_" + name + "_error", "true");
            throw new AppException(MSG_INVALID_FLOAT);
        }
    }


    /**
     * Converts a String to an Float.
     *
     * @param input      String to convert
     * @param allowNull  True if nulls should be allowed, if false, null input will throw an exception
     * @return Float   The input String parsed to an Float
     * @exception AppException  If the String cannot be parsed
     */
    public static Float convertFloat(String input, boolean allowNulls) throws AppException {
        if (allowNulls && Utils.isEmpty(input)) return null;

	return convertFloat(input);
    }


    /**
     * Converts a String to an Double.
     *
     * @exception AppException  If the String cannot be parsed. Removes commas, but leaves decimals unchanged.
     */
    public static Double convertDouble(String input) throws AppException {
	input = StringUtils.replace(input, ",", "");
	input = StringUtils.trim(input);
        try {
            return new Double(input);
        } catch (Exception e) {
            throw new AppException(MSG_INVALID_DOUBLE);
        }
    }


    /**
     * Converts a String to an Double.
     *
     * @param input      String to convert
     * @param allowNull  True if nulls should be allowed, if false, null input will throw an exception
     * @return Double   The input String parsed to an Double
     * @exception AppException  If the String cannot be parsed
     */
    public static Double convertDouble(String input, boolean allowNulls) throws AppException {
        if (allowNulls && Utils.isEmpty(input)) return null;

	return convertDouble(input);
    }


    /**
     * Converts a String in the format mm/dd/yyyy into a Date.
     *
     * @param input      String to convert
     * @param allowNulls True if nulls should be allowed, if false, null input will throw an exception
     * @return Date      The input String parsed to a Date
     * @exception AppException  If the String cannot be parsed  
     */
    public static Date convertDate(String input, boolean allowNulls) throws AppException {
	if (allowNulls && Utils.isEmpty(input)) return null;

	//parse the entered date and return the value      
	
	//check for proper input length                 
	if (input.length() != 10) throw new AppException(MSG_INVALID_DATE);

	//check for valid dates
	try {
	    return CALENDAR_FORMAT.parse(input);
	} catch (ParseException fe) {
	    log.debug("Error parsing date: " + input);
	    
	    throw new AppException(MSG_INVALID_DATE);
	}
    }


    /**
     * Converts a String in the format mm/dd/yyyy into a Date.
     *
     * @param name       The name of the request parameter to convert       
     * @param request    The request to get the Date from
     * @return Date      The input String parsed to a Date
     * @exception AppException  If the String cannot be parsed  
     */
    public static Date convertDate(String name, HttpServletRequest request) throws AppException {
	String input = request.getParameter(name);
	if (Utils.isEmpty(input)) return null;

	//parse the entered date and return the value      
	
	//check for proper input length                 
	if (input.length() != 10) throw new AppException(MSG_INVALID_DATE);

	//check for valid dates
	try {
	    return CALENDAR_FORMAT.parse(input);
	} catch (ParseException fe) {
	    log.debug("Error parsing date: " + input);
	    
	    throw new AppException(MSG_INVALID_DATE);
	}
    }


    /**
     * Converts a String in the format mm/dd/yyyy into a Date. Null input will return null 
     * output without an exception being thrown.
     *
     * @param input      String to convert
     * @return Date      The input String parsed to a Date
     * @exception AppException  If the String cannot be parsed  
     */
    public static Date convertDate(String input) throws AppException {
	return convertDate(input, true);
    }    


    /**
     * Converts a String into a Boolean. If the input String is "true" a true Boolean will 
     * be returned. Otherwise the return value will be false
     *
     * @param input      String to convert
     * @return Boolean   The input String parsed to a Boolean
     */
    public static Boolean convertBoolean(String input) throws AppException {
	return Boolean.valueOf(input);
    }    


    /**
     * Checks for a valid email address.
     *
     * @throws AppException   If the email address is invalid
     */
    public static void checkValidEmail(String toCheck) throws AppException {
        if ( !EmailUtils.isValidAddress(toCheck) ) throw new AppException( MSG_INVALID_EMAIL + toCheck );

    }


    /**
     * Checks for valid email addresses in a comma separated list.
     *
     * @throws AppException   If any email address is invalid
     */
    public static void checkValidEmailList(String toCheck) throws AppException {
        StringTokenizer st = new StringTokenizer (toCheck, " \t\n\f\r,");
	while (st.hasMoreTokens()) {
	    checkValidEmail(st.nextToken());
	}
    }


    /**
     * Checks whether a request parameter is a valid email address
     * 
     * @param name     The name of the request parameter to check
     * @param request  The request to get the String from                   
     * @return String  The value of the request parameter
     * @exception AppException   If the String cannot be parsed
     */
    public static String checkValidEmail(String name, HttpServletRequest request) throws AppException {
	String toCheck = request.getParameter(name);

	if ( !EmailUtils.isValidAddress(toCheck) ) {
	    request.setAttribute("error_" + name + "_error", "true");
            throw new AppException( MSG_INVALID_EMAIL + toCheck );
        }
	
	return toCheck;
    }


    //REQUEST UTILITY METHODS ------------------------------------------------------------------

    /**
     * Returns true if the passed parameter name exists in the request. Useful for 
     * determining whether a form submission was firstName=[blank] or whether there was
     * no firstName field submitted at all.
     *
     * @param name          The name of the param to check for
     * @param request       The request to check
     * @return boolean      True if the named parameter exists in the request
     */
    public static boolean parameterExists(String name, HttpServletRequest request) {
	//sanity check
	if (Utils.isEmpty(name) || request == null) return false;

	String param = request.getParameter(name);

	return (param != null) ;
    }


    /**
     * Returns a List of request Parameter NAMES that begin with a specified String.
     * By default, the matchString will be trimmed from the names in the output List
     *
     * @param request       The request to build the List from
     * @param matchString   The matching String
     * @return List         A list of attributes and values that begin with the specified String
     */
    public static List<String> getParametersByName(javax.servlet.ServletRequest request, String matchString) {
	return getParametersByName(request, matchString, true);
    }
    
    
    /**
     * Returns a List of request Parameter NAMES that begin with a specified String
     *
     * @param request       The request to build the List from
     * @param matchString   The matching String
     * @param trim          If true, the names in the output will have the match String trimmed
     * @return List         A list of parameter NAMES that begin with the specified String
     */
    public static List<String> getParametersByName(javax.servlet.ServletRequest request, String matchString, boolean trim) {
	
	Enumeration e = request.getParameterNames();
	ArrayList<String> namedParameters = new ArrayList<String>(10);
	
	while ( e.hasMoreElements() ) {
	    String parameterName = (String)e.nextElement();
	    if ( parameterName.startsWith( matchString ) ) {
		if (trim) parameterName = parameterName.substring( matchString.length() );
		
		namedParameters.add( parameterName );  				
	    }
	}	
	
	return namedParameters;  		
    }


    /**
     * Returns a List of Map key NAMES that begin with a specified String.
     * By default, the matchString will be trimmed from the names in the output List
     *
     * @param params        The Map to build the List from
     * @param matchString   The matching String
     * @return List         A list of key NAMES that begin with the specified String
     */
    public static List<String> getParametersByName(Map<String,String> params, String matchString) {
	return getParametersByName(params, matchString, true);
    }
    
    
    /**
     * Returns a List of Map key NAMES that begin with a specified String
     *
     * @param request       The Map to build the List from
     * @param matchString   The matching String
     * @param trim          If true, the names in the output will have the match String trimmed
     * @return List         A list of key NAMES that begin with the specified String
     */
    public static List<String> getParametersByName(Map<String,String> params, String matchString, boolean trim) {

	Set keys = params.keySet();
	ArrayList<String> namedParameters = new ArrayList<String>(keys.size());
	
	Iterator i = keys.iterator();
	while ( i.hasNext() ) {
	    String parameterName = (String)i.next();
	    if ( parameterName.startsWith( matchString ) ) {
		if (trim) parameterName = parameterName.substring( matchString.length() );
		
		namedParameters.add( parameterName );  				
	    }
	}	
	
	return namedParameters;  		
    }


    /**
     * Returns the original parameter String that was submitted iwth the request. 
     * For example, if the request was: http://www.mycompany.com/servlet?foo=bar&bar=baz
     * This method will return: foo=bar&bar=baz
     */
    public static String getParameterString(HttpServletRequest request) {
	if (request == null) return "";

	Map params = request.getParameterMap();

	StringBuilder output = new StringBuilder(params.size() * 32);

	Iterator names = params.keySet().iterator();
	while (names.hasNext()) {
	    String name     = (String)names.next();

	    String[] values = (String[])params.get(name);

	    for (int i = 0; i < values.length; i++) {
		String value = values[i];

		output.append(name);
		output.append("=");
		output.append(value);

		if (i < values.length || names.hasNext()) output.append("&");
	    }
	}

	return output.toString();
    }


    /**                                                                                                                      
     * Returns the specified cookie, or <tt>null</tt> if the cookie                                                         
     * does not exist. Note: because of the way that cookies are implemented                                             
     * it's possible for multiple cookies with the same name to exist (but with                                             
     * different domain values). This method will return the first cookie that                                             
     * has a name match.                                                                                                       
     *                                                                                                                     
     * @param request a HttpServletRequest object.                                                                            
     * @param name the name of the cookie.                                                                                   
     * @return the Cookie object if it exists, otherwise <tt>null</tt>.       
     */
    public static Cookie getCookie(HttpServletRequest request, String name) {
        Cookie cookies[] = request.getCookies();

        // Return null if there are no cookies or the name is invalid.                                                   
        if (cookies == null || Utils.isEmpty(name)) {
            return null;
        }

        // Otherwise, we  do a linear scan for the cookie.                                                              
        for (int i = 0; i < cookies.length; i++) {
            // If the current cookie name matches the one we're looking for, we've                                         
            // found a matching cookie.                                         
            Cookie cookie = cookies[i];
            if (cookie.getName().equals(name)) {
		//log.debug("MATCHING COOKIE!");

                return cookie;
            }
        }
        return null;
    }


    /**                                                                                                                      
     * Returns the specified cookie, or <tt>null</tt> if the cookie                                                         
     * does not exist. Note: because of the way that cookies are implemented                                             
     * it's possible for multiple cookies with the same name to exist (but with                                             
     * different domain values). This method will return the first cookie that                                             
     * has a name match.                                                                                                       
     *                                                                                                                     
     * @param request a HttpServletRequest object.                                                                            
     * @param name    The name of the cookie.                                                                                   
     * @param path    The path of the cookie.                                                                                   
     * @return the Cookie object if it exists, otherwise <tt>null</tt>.       
     */
    public static Cookie getCookie(HttpServletRequest request, String name, String path) {
        Cookie cookies[] = request.getCookies();

	if (Utils.isEmpty(path)) return getCookie(request, name);

        // Return null if there are no cookies or the name is invalid.                                                   
        if (cookies == null || Utils.isEmpty(name)) {
            return null;
        }

        // Otherwise, we  do a linear scan for the cookie.                                                              
        for (int i = 0; i < cookies.length; i++) {
            // If the current cookie name matches the one we're looking for, we've                                         
            // found a matching cookie.                                         
            Cookie cookie = cookies[i];
	    log.debug("CHECKING COOKIE: " + cookie.getName() + " DOMAIN: " + cookie.getDomain() + " PATH: " + cookie.getPath());
            if (cookie.getName().equals(name) && cookie.getPath().equals(path)) {
		//log.debug("MATCHING COOKIE!");

                return cookie;
            }
        }
        return null;
    }


    //RESPONSE UTILITY METHODS -----------------------------------------------------------------

    /** 
     * Sets headers for downloading a file: 
     * 
     * <li>Encodes the filename to allow for int'l characters
     * <li>Sets content type to: application/x-download
     * <li>Sets content length
     */
    public static void setDownloadHeaders(HttpServletRequest request, HttpServletResponse response, Upload download) {

	log.debug("setDownloadHeaders: download: " + download);

	//sanity check 
	if (download == null) return;

	//set files for utf-8 encoding                                                                                       
	String userAgent = request.getHeader("User-Agent");
	String filename  = download.getFileName();
	log.debug("filename: " + filename);

	String encodedFileName = org.htmlparser.util.Translate.decode(filename);
	log.debug("encodedFileName: " + encodedFileName);

	try {
	    if (userAgent.contains("MSIE") || userAgent.contains("Opera")) {
		encodedFileName = java.net.URLEncoder.encode(encodedFileName, "UTF-8");
	    } else {
		encodedFileName = "=?UTF-8?B?" + new String(Base64.encode(encodedFileName.getBytes("UTF-8")), "UTF-8") + "?=";
	    }
	    log.debug("utf-8 encodedFileName: " + encodedFileName);
	    response.setHeader("Content-Disposition","attachment;filename=\"" + encodedFileName + "\"");
	} catch (java.io.UnsupportedEncodingException uee) {
	    //no-op. We know UTF-8 is a valid encoding! 
	}

	//make sure IE users can download under https
	response.setHeader("Pragma", "public");
	response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
	response.setDateHeader("Expires", 0);

	response.setContentType("application/x-download");                                                   
	response.setContentLength( (int)FileUtils.getFileSize(location + download.getLink()) );
    }


    /** 
     * Sets headers for downloading a file: 
     * 
     * <li>Encodes the filename to allow for int'l characters
     * <li>Sets content type to: application/x-download
     * <li>Sets content length
     */
    public static void setDownloadHeaders(HttpServletRequest request, HttpServletResponse response, String filename) {

	log.debug("setDownloadHeaders: filename: " + filename);

	//sanity check 
	if (Utils.isEmpty(filename)) filename = "download";
	log.debug("filename: " + filename);

	//set files for utf-8 encoding                                      
	String userAgent = request.getHeader("User-Agent");

	String encodedFileName = org.htmlparser.util.Translate.decode(filename);
	log.debug("encodedFileName: " + encodedFileName);

	try {
	    if (userAgent.contains("MSIE") || userAgent.contains("Opera")) {
		encodedFileName = java.net.URLEncoder.encode(encodedFileName, "UTF-8");
	    } else {
		encodedFileName = "=?UTF-8?B?" + new String(Base64.encode(encodedFileName.getBytes("UTF-8")), "UTF-8") + "?=";
	    }
	    log.debug("utf-8 encodedFileName: " + encodedFileName);
	    response.setHeader("Content-Disposition","attachment;filename=\"" + encodedFileName + "\"");
	} catch (java.io.UnsupportedEncodingException uee) {
	    //no-op. We know UTF-8 is a valid encoding! 
	}

	//make sure IE users can download under https
	response.setHeader("Pragma", "public");
	response.setHeader("Cache-Control", "must-revalidate, post-check=0, pre-check=0");
	response.setDateHeader("Expires", 0);

	response.setContentType("application/x-download");                                                   
	//	response.setContentLength( (int)FileUtils.getFileSize(location + download.getLink()) );
    }


    //NET UTILITY METHODS ----------------------------------------------------------------------

    /**
     * Loads an URL and returns the contents as a String.
     */
    public static String loadUrl(URL url) {
	String output = "";

	InputStream is   = null;
	InputStreamReader reader = null;
	//	OutputStream out = new ByteArrayOutputStream(1000);
	StringWriter out = new StringWriter();

	try {
            URLConnection connection = url.openConnection();
            if (connection instanceof HttpURLConnection) {
		HttpURLConnection httpConnection = (HttpURLConnection)connection;
		httpConnection.connect();

		int response = httpConnection.getResponseCode();
		log.debug("BaseServlet.loadUrl() Response: " + response);
		log.debug("                          Type: " + httpConnection.getContentType());
		log.debug("                      Encoding: " + httpConnection.getContentEncoding());

		is = httpConnection.getInputStream();

		reader = new InputStreamReader(is, "utf-8");

		//		byte[] buffer = new byte[1024];
		char[] buffer = new char[1024];
		int k = 0;
		while ( (k = reader.read(buffer) ) != -1) {
		    out.write( buffer, 0, k );
		}

		output = out.toString();

	    } else if (connection instanceof FtpURLConnection) {
		FtpURLConnection ftpConnection = (FtpURLConnection)connection;
		ftpConnection.connect();

		is = ftpConnection.getInputStream();

		reader = new InputStreamReader(is, "utf-8");

		char[] buffer = new char[1024];
		int k = 0;
		while ( (k = reader.read(buffer) ) != -1) {
		    out.write( buffer, 0, k );
		}

		output = out.toString();

            } else {
		log.debug("CONNECTION NOT HTTP: " + connection);
	    }
	} catch (IOException e) {
            log.debug("", e);

	} finally {
	    closeStream(is);
	    closeStream(reader);
	    closeStream(out);

	}

	return output;
    }


    /**
     * Loads an URL and returns the contents as a String.
     */
    public static String loadUrl(String urlString) {
	try {
	    URL url = new URL(urlString);
	    return loadUrl(url);
	} catch (MalformedURLException e) {
            log.debug("", e);
        }
	
	return "";
    }


    /**
     * Posts to a URL and returns the contents as a String.
     */
    public static String postUrl(String urlString, Map<String, String> params) {
	try {
            URL url = new URL(urlString);
            return postUrl(url, params);
        } catch (MalformedURLException e) {
            log.debug("", e);
        }

        return "";
    }


    /**
     * Posts to a URL and returns the contents as a String.
     */
    public static String postUrl(URL url, Map<String, String> params) {

	//sanity check
	if (url == null) return "";

	URLConnection urlConn;
	DataOutputStream printout = null;
	BufferedReader input      = null;

	try {
	    // Open URL connection
	    urlConn = url.openConnection();

	    //if we are dealing with http, follow redirects
	    if (urlConn instanceof HttpURLConnection) {
		((HttpURLConnection)urlConn).setFollowRedirects(true);
	    }

	    //if this is https, should we verify hostnames?
	    if (urlConn instanceof HttpsURLConnection && !verifyHostnames) {
		((HttpsURLConnection)urlConn).setHostnameVerifier ( new HostnameVerifier () {  
		    @Override
			public boolean verify ( String hostname, SSLSession session ) {  
			    return true; 
			}  
		    }  ) ; 
	    }
	    
	    // Let the run-time system (RTS) know that we want input and output
	    urlConn.setDoInput(true);
	    urlConn.setDoOutput(true);
	    
	    // No caching, we want the real thing.
	    urlConn.setUseCaches(false);
	    
	    // Specify the content type.
	    urlConn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
	    
	    // Send POST 
	    printout = new DataOutputStream(urlConn.getOutputStream());
	    
	    if (params != null) {
		Iterator i = params.keySet().iterator();
		StringBuilder content = new StringBuilder(512);
		
		while (i.hasNext()) {
		    String param = (String)i.next();
		    String value = params.get(param);
		    
		    if (Utils.isEmpty(value)) continue;

		    content
			.append( param)
			.append("=")
			.append( URLEncoder.encode(value, "UTF-8"));
		    
		    if (i.hasNext()) content.append("&");
		}
		
		printout.writeBytes (content.toString());
	    }
	    
	    printout.flush();
	    //	    printout.close();

	    // Get response
	    
	    input = new BufferedReader( new InputStreamReader( urlConn.getInputStream() ) );
	    //input = new DataInputStream( urlConn.getInputStream() );
	    
	    StringBuilder output = new StringBuilder(512);
	    String str;
	    while (null != ((str = input.readLine()))) {
		
		output.append (str);
	    }

	    return output.toString();

	} catch (Exception e) {
	    log.debug("", e);

	} finally {
	    //clean up
	    closeStream(printout);
	    closeStream(input);

	}

	return "";
    }
    

    /**
     * Posts XML to a URL and returns the contents as a String.
     */
    public static String postXml(String urlString, String username, String password, String xml) {
	try {
	    URL url = new URL(urlString);
	    return postXml(url, username, password, xml);
        } catch (MalformedURLException e) {
            log.debug("", e);
        }

        return "";
    }


    /**
     * Posts XML to a URL and returns the contents as a String.
     */
    public static String postXml(URL url, String username, String password, String xml) {

	//sanity check
	if (url == null) return "";

	URLConnection urlConn;
	DataOutputStream printout = null;
	BufferedReader input      = null;

	try {
	    // Open URL connection
	    urlConn = url.openConnection();

	    //if we are dealing with http, follow redirects
	    if (urlConn instanceof HttpURLConnection) {
		((HttpURLConnection)urlConn).setFollowRedirects(true);
	    }

	    //if this is https, should we verify hostnames?
	    if (urlConn instanceof HttpsURLConnection && !verifyHostnames) {
		((HttpsURLConnection)urlConn).setHostnameVerifier ( new HostnameVerifier () {  
		    @Override
			public boolean verify ( String hostname, SSLSession session ) {  
			    return true; 
			}  
		    }  ) ; 
	    }
	    
	    // Let the run-time system (RTS) know that we want input and output
	    urlConn.setDoInput(true);
	    urlConn.setDoOutput(true);
	    
	    // No caching, we want the real thing.
	    urlConn.setUseCaches(false);
	    
	    // Specify the content type.
	    urlConn.setRequestProperty("Content-Type", "application/xml");
	    
	    //if we have a username and password specified, add them to the request
	    if (!Utils.isEmpty(username) && !Utils.isEmpty(password)) {
		String userPass = username + ":" + password;
		urlConn.setRequestProperty("Authorization", 
					   "Basic " + Base64.encodeToString(userPass.getBytes()));
	    }

	    // Send POST 
	    printout = new DataOutputStream(urlConn.getOutputStream());
	    printout.writeBytes(xml);
	    printout.flush();

	    // Get response	    
	    input = new BufferedReader( new InputStreamReader( urlConn.getInputStream() ) );
	    
	    StringBuilder output = new StringBuilder(512);
	    String str;
	    while (null != ((str = input.readLine()))) {		
		output.append (str);
	    }

	    return output.toString();

	} catch (Exception e) {
	    log.debug("", e);

	} finally {
	    //clean up
	    closeStream(printout);
	    closeStream(input);

	}

	return "";
    }

}
